
package com.x52im.rbchat.logic.chat_group.utils;

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

import com.eva.android.widget.WidgetUtils;
import com.google.gson.Gson;
import com.x52im.rainbowchat.im.dto.QuoteMeta;
import com.x52im.rbchat.MyApplication;
//import com.x52im.rainbowchat.im.dto.MsgBody4Friend;
import com.x52im.rbchat.R;
import com.x52im.rainbowchat.common.dto.cnst.UserProtocalsType;
import com.x52im.rainbowchat.http.logic.dto.GroupEntity;
import com.x52im.rainbowchat.im.dto.CMDBody4GroupNameChangedNotification;
import com.x52im.rainbowchat.im.dto.CMDBody4MyselfBeInvitedGroupResponse;
import com.x52im.rainbowchat.im.dto.MsgBody4Friend;
import com.x52im.rainbowchat.im.dto.MsgBody4Group;
import com.x52im.rainbowchat.im.dto.RevokedMeta;
import com.x52im.rbchat.cache.AlarmsProvider;
import com.x52im.rbchat.cache.GroupsProvider;
import com.x52im.rbchat.logic.chat_root.model.ContactMeta;
import com.x52im.rbchat.logic.chat_root.model.FileMeta;
import com.x52im.rbchat.logic.chat_root.model.LocationMeta;
import com.x52im.rbchat.logic.chat_root.model.Message;
import com.x52im.rbchat.logic.chat_root.model.MessageExt;
import com.x52im.rbchat.network.im.SendDataHelper;

import net.x52im.mobileimsdk.server.protocal.Protocal;

import java.util.Arrays;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

/**
 * 群聊天相关消息/指令的发送和解析方法。
 *
 * @author Lu
 */
public class GMessageHelper
{
    private final static String TAG = GMessageHelper.class.getSimpleName();

	//------------------------------------------------------------------------ 收到的消息/协议解析方法 START
	/**
	 * 解析群聊聊天消息：由服务端转发给接收人B的【步骤2/2】.
	 *
	 * <p>
	 * 当然，此消息被接收到的前提条件是B用户此时是在线的（否则临时聊天消息将服务端被存储到DB中（直到本地用户下次上线））。
	 *
	 * @param originalMsg
	 * @return
	 */
	public static MsgBody4Group parseGroupChatMsg_SERVER$TO$B_Message(String originalMsg)
	{
		System.out.println("!!!!!!收到服务端发过来的群聊聊天信息：" + originalMsg);
		return new Gson().fromJson(originalMsg, MsgBody4Group.class);
	}

	/**
	 * 解析群聊系统指令：“我”加群成功后通知“我”（即被加群者）（由Server发出），
	 * 通知接收人可能是在创建群或群建好后邀请进入的.
	 *
	 * @param originalMsg
	 * @return
	 */
	public static CMDBody4MyselfBeInvitedGroupResponse parseResponse4GroupSysCMD4MyselfBeInvited(String originalMsg)
	{
		System.out.println("!!!!!!收到服务端发过来的群聊指令be_invited：" + originalMsg);
		return new Gson().fromJson(originalMsg, CMDBody4MyselfBeInvitedGroupResponse.class);
	}

	/**
	 * 解析群聊系统指令：群聊时，向所有(除修改者)的群员通知群名被修改的通知协议内容（由Server发出），
	 * 通知接收人可能是在创建群或群建好后邀请进入的.
	 *
	 * @param originalMsg
	 * @return
	 */
	public static CMDBody4GroupNameChangedNotification parseResponse4GroupSysCMD4GroupNameChanged(String originalMsg)
	{
		System.out.println("!!!!!!收到服务端发过来的群聊指令gname_changed：" + originalMsg);
		return new Gson().fromJson(originalMsg, CMDBody4GroupNameChangedNotification.class);
	}

//	/**
//	 * 解析群聊系统指令：“别人”加群成功后通知其它群员（由Server发出）。
//	 *
//	 * @param originalMsg
//	 * @return
//	 */
//	public static CMDBody4SomebodyJoinedGroupNotification parseResponse4GroupSysCMD4SomebodyJoined(String originalMsg)
//	{
//		System.out.println("!!!!!!收到服务端发过来的群聊指令new_joined：" + originalMsg);
//		return new Gson().fromJson(originalMsg, CMDBody4SomebodyJoinedGroupNotification.class);
//	}
	//------------------------------------------------------------------------ 收到的消息/协议解析方法 END


	//------------------------------------------------------------------------ 对外公开的异步消息发送方法 START
	/**
	 * 将指导定的图片消息发送给指定群组（异步方式）.
     * <p>
     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
     * </p>
	 *
	 * @param message 文本消息，如果该文本为null或空字符串则不会真正执行发送过程
	 * @param at 此条消息的"@"对象数组（数组单元是被“@”者的uid）
	 * @param quoteMeta 消息引用信息，此字段可为空（表示本条无引用消息）
	 */
	public static void sendPlainTextMessageAsync(final Activity activity
			, final String toGid
			, final String message
			, List<String> at
			, final QuoteMeta quoteMeta
			, final Observer sucessObsExtra)
	{
		// 先生成指纹码
		final String fingerPring = Protocal.genFingerPrint();

		// 消息指令通过网络发送成功后要通知的观察者
		Observer sendSucessObs = (o, arg) -> {
			// 消息数据对象（用于ui界面显示）
			Message m = MessageExt.createChatMsgEntity_OUTGO_TEXT(message, fingerPring);
			// 尝试设置消息引用信息（当前仅针对文本消息有意义），以便用于ui显示
			m.setQuoteMeta(quoteMeta);

			// 消息发送成功后，将此消息数据放到聊天列表的数据模型中（让UI进行显示）
			GChatDataHelper.addChatMessageData_outgoing(activity, toGid, m);

			// 消息发送调用者的额外要做的事
			if(sucessObsExtra != null)
				sucessObsExtra.update(null, m);
		};

		// 调用真正的消息指令发送方法
		sendMessageAsync(activity, toGid, MsgBody4Group.TYPE_TEXT, message, at, fingerPring, quoteMeta, sendSucessObs);
	}

	/**
	 * 将指导定的图片消息发送给指定群组（异步方式）.
     * <p>
     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
     * </p>
	 *
	 * @param activity
	 * @param imageFilePath 文本消息，也即是图片的文件名，如果该文本为null或空字符串则不会真正执行发送过程
	 * @param sucessObsExtra 消息发送成功后要通知的观察者，本参数可为null
	 */
	public static void sendImageMessageAsync(final Activity activity
			, String toGid, String imageFilePath, String fingerPring, final Observer sucessObsExtra)
	{
		// 消息指令通过网络发送成功后要通知的观察者
		Observer sendSucessObs = (o, arg) -> {
			// 发送图片消息成功后什么也不用做（其它在本异步线程执行前图片消息就已经放入到聊天列表了，这是与普通消息最大的区别哦）
			// do nothing

			// 消息发送调用者的额外要做的事
			if(sucessObsExtra != null)
				sucessObsExtra.update(null, null);
		};

		// 调用真正的消息指令发送方法
		sendMessageAsync(activity, toGid, MsgBody4Group.TYPE_IMAGE, imageFilePath, null, fingerPring, sendSucessObs);
	}

	/**
	 * 将指导定的语音消息发送给指定群组（异步方式）.
     * <p>
     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
     * </p>
	 *
	 * @param activity
	 * @param imageFilePath 文本消息，也即是图片的文件名，如果该文本为null或空字符串则不会真正执行发送过程
	 * @param sucessObsExtra 消息发送成功后要通知的观察者，本参数可为null
	 */
	public static void sendVoiceMessageAsync(final Activity activity, String toGid
            , String imageFilePath, String fingerPring, final Observer sucessObsExtra)
	{
		// 消息指令通过网络发送成功后要通知的观察者
		Observer sendSucessObs = (o, arg) -> {
			// 发送语音消息成功后什么也不用做（其它在本异步线程执行前语音消息就已经放入到聊天列表了，这是与普通消息最大的区别哦）
			// do nothing

			// 消息发送调用者的额外要做的事
			if(sucessObsExtra != null)
				sucessObsExtra.update(null, null);
		};

		// 调用真正的消息指令发送方法
		sendMessageAsync(activity, toGid, MsgBody4Group.TYPE_VOICE, imageFilePath, null, fingerPring, sendSucessObs);
	}

	/**
	 * 将指定的文件消息发送给聊天中的群（异步方式）.
     * <p>
     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
     * </p>
	 *
	 * @param content 文件消息的内容就是FileMeta对象
	 * @return code:0 表示发送成功，否则错误码
	 */
	public static void sendFileMessageAsync(final Activity activity
			, final String toGid, final FileMeta content, final String fingerPring, final Observer sucessObsExtra)
	{
		// 消息指令通过网络发送成功后要通知的观察者
		Observer sendSucessObs = (o, arg) -> {
			// 发送文件消息成功后什么也不用做（其它在本异步线程执行前语音消息就已经放入到聊天列表了，这是与普通消息最大的区别哦）
			// do nothing

			// 消息发送调用者的额外要做的事
			if(sucessObsExtra != null)
				sucessObsExtra.update(null, null);
		};

		// 调用真正的消息指令发送方法
		sendMessageAsync(activity, toGid, MsgBody4Group.TYPE_FILE, new Gson().toJson(content), null, fingerPring, sendSucessObs);
	}

    /**
     * 将指定的短视频消息发送给聊天中的群（异步方式）.
     * <p>
     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
     * </p>
     *
     * @param content 短视频消息的内容就是FileMeta对象
     * @return code:0 表示发送成功，否则错误码
     */
    public static void sendShortVideoMessageAsync(final Activity activity
            , final String toGid, final FileMeta content, final String fingerPring, final Observer sucessObsExtra)
    {
		// 消息指令通过网络发送成功后要通知的观察者
		Observer sendSucessObs = (o, arg) -> {
			// 发送短视频消息成功后什么也不用做（其它在本异步线程执行前语音消息就已经放入到聊天列表了，这是与普通消息最大的区别哦）
			// do nothing

			// 消息发送调用者的额外要做的事
			if(sucessObsExtra != null)
				sucessObsExtra.update(null, null);
		};

		// 调用真正的消息指令发送方法
        sendMessageAsync(activity, toGid, MsgBody4Group.TYPE_SHORTVIDEO, new Gson().toJson(content), null, fingerPring, sendSucessObs);
    }

	/**
	 * 将指定的个人名片消息发送给聊天中的群（异步方式）.
	 * <p>
	 * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
	 * </p>
	 *
	 * @param content 文件消息的内容就是FileMeta对象
	 * @return code:0 表示发送成功，否则错误码
	 * @see #sendMessageAsync(Activity, String, int, String, String, Observer)
	 * @since 6.0
	 */
	public static void sendContactMessageAsync(final Activity activity, final String toGid, final ContactMeta content, final Observer sucessObsExtra)
	{
		// 先生成指纹码
		final String fingerPring = Protocal.genFingerPrint();

		// 消息指令通过网络发送成功后要通知的观察者
		Observer sendSucessObs = (o, arg) -> {
			// 消息发送成功后，将此消息数据放到聊天列表的数据模型中（让UI进行显示）
			Message m = GChatDataHelper.addChatMessageData_outgoing(activity, toGid, MessageExt.createChatMsgEntity_OUTGO_CONTACT(content, fingerPring));
			// 消息发送调用者的额外要做的事
			if(sucessObsExtra != null)
				sucessObsExtra.update(null, m);
		};

		// 调用真正的消息指令发送方法
		sendMessageAsync(activity, toGid, MsgBody4Friend.TYPE_CONTACT, new Gson().toJson(content), null, fingerPring, sendSucessObs);
	}

	/**
	 * 将指定的位置消息发送给聊天中的群（异步方式）.
	 * <p>
	 * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
	 * </p>
	 *
	 * @param content 文件消息的内容就是LocationMeta对象
	 * @see #sendMessageAsync(Activity, String, int, String, String, Observer)
	 * @since 6.0
	 */
	public static void sendLocationMessageAsync(final Activity activity
			, final String toGid, final LocationMeta content, final Observer sucessObsExtra)
	{
		// 先生成指纹码
		final String fingerPring = Protocal.genFingerPrint();

		// 消息指令通过网络发送成功后要通知的观察者
		Observer sendSucessObs = (o, arg) -> {
			// 消息发送成功后，将此消息数据放到聊天列表的数据模型中（让UI进行显示）
			Message m = GChatDataHelper.addChatMessageData_outgoing(activity, toGid, MessageExt.createChatMsgEntity_OUTGO_LOCATION(content, fingerPring));

			// 消息发送调用者的额外要做的事
			if(sucessObsExtra != null)
				sucessObsExtra.update(null, m);
		};

		// 调用真正的消息指令发送方法
		sendMessageAsync(activity, toGid, MsgBody4Friend.TYPE_LOCATION, new Gson().toJson(content), null, fingerPring, sendSucessObs);
	}

	/**
	 * "撤回"消息（异步方式）.
	 *
	 * @param content 消息撤回指令的内容就是RevokedMeta对象
	 * @return 返回"撤回"指令自已的指纹码
	 * @see #sendMessageAsync(Activity, String, int, String, String, Observer)
	 */
	public static void sendRevokeMessageAsync(final Activity activity
			, String fingerPrint, final String toGid, final RevokedMeta content, final Observer sucessObsExtra)
	{
		// 消息指令通过网络发送成功后要通知的观察者
		Observer sendSucessObs = (o, arg) -> {
			// 注意：由于消息"撤回"指令需要等对方的应答回来（也就是对方收到撤回指令后）才能做本地的真正"撤回"逻辑，
			//      所以此观察者中无法立即进行"撤回"处理，余下的本地撤回逻辑将由 MessageRevoingManager来实现！

			// 消息发送调用者的额外要做的事
			if(sucessObsExtra != null)
				sucessObsExtra.update(null, null);
		};

		// 调用真正的消息指令发送方法
		sendMessageAsync(activity, toGid, MsgBody4Friend.TYPE_REVOKE, new Gson().toJson(content), null, fingerPrint, sendSucessObs);
	}

	/**
	 * 将指定的消息发送给指定群组（异步方式）.
	 * <p>
	 * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
	 * </p>
	 *
	 * @param messageType 参见  {@link MsgBody4Group}中的文本消息类型
	 * @param message 文本消息，如果该文本为null或空字符串则不会真正执行发送过程
	 * @param at 此条消息的"@"对象数组（数组单元是被“@”者的uid）
	 * @param quoteMeta 消息引用信息（当前仅用于文本消息时），此字段可为空（表示本条无引用消息）
	 */
	private static void sendMessageAsync(final Activity activity
			, final String toGid, final int messageType, final String message
			, final List<String> at
			, final String fingerPring
			, Observer sucessObs)
	{
		GMessageHelper.sendMessageAsync(activity, toGid, messageType, message, at, fingerPring, null, sucessObs);
	}

	/**
	 * 将指定的消息发送给指定群组（异步方式）.
     * <p>
     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
     * </p>
	 *
	 * @param messageType 参见  {@link MsgBody4Group}中的文本消息类型
	 * @param message 文本消息，如果该文本为null或空字符串则不会真正执行发送过程
	 * @param at 此条消息的"@"对象数组（数组单元是被“@”者的uid）
	 * @param quoteMeta 消息引用信息（当前仅用于文本消息时），此字段可为空（表示本条无引用消息）
	 */
	private static void sendMessageAsync(final Activity activity
			, final String toGid, final int messageType, final String message
			, final List<String> at
			, final String fingerPring
			, final QuoteMeta quoteMeta
			, Observer sucessObs)
	{
		if (message != null && message.length() > 0)
		{
			final GroupEntity currentChattingGe = MyApplication.getInstance(activity).getIMClientManager().getGroupsProvider().getGroupInfoByGid(toGid);

			// 是普通群聊消息
			if(!GroupEntity.isWorldChat(toGid)) {
				// 出现此种情形的可能发生于：我已被踢出或从群聊中删除，但仍从首页"消息"列表中点击遗留的Alarms进来的！
				if(currentChattingGe == null) {
					WidgetUtils.showToastLong(activity, "您已不在该群组中，无法发送消息哦！", WidgetUtils.ToastType.WARN);
					return;
				}
			}

			// 新聊天框架中的网络数据发送需要放到异步线程里（Android2.3以上版本要求网络交互不允许发生在主线程里）
			new AsyncTask<Object, Integer, Integer>(){
				// 数据成功发出去后要通知的观察者（目前，新聊天框架在有丢包重传机前，“成功发出”只意味着发出去但不意味着一定能被收到）
				private Observer sucessObs = null;

				@Override
				protected Integer doInBackground(Object... params)
				{
					if(params != null)
						sucessObs = (Observer)params[0];

					MsgBody4Group tcmd = GMessageHelper.constructGroupChatMsgBodyForSend(activity, fingerPring, messageType, toGid, message, at);
					// 尝试设置消息引用信息
					tcmd.setQuoteMeta(quoteMeta);
					return GMessageHelper.sendBBSChatMsg_A$TO$SERVER_Message(activity, tcmd, true, fingerPring);
//					return GMessageHelper.sendChatMessage(activity, messageType, toGid, message, at, fingerPring);
				}

				@Override
				protected void onPostExecute(Integer code)
				{
					// 消息发送成功
					if(code == 0)
					{
						if(sucessObs != null)
							sucessObs.update(null, null);

						// 是普通群聊消息
						if(!GroupEntity.isWorldChat(toGid))
						{
							// 将本地用户主动发出的群聊消息也入到首页消息栏里.
							// * 像微信这样的IM里，为了方便下次查看，自已主动发的消息也放到了首页消息栏（而不限于收到的消息），为了方便
							AlarmsProvider.addAGroupChatMsgAlarmForLocal(activity, messageType, toGid, currentChattingGe.getG_name(), message);
						}
					}
					else
					{
					    Log.e(TAG, "网络发送数据失败，错误码：code="+code);
                        WidgetUtils.showToast(activity
                                , activity.getString(R.string.chat_message_send_error), WidgetUtils.ToastType.ERROR);
                    }
				}
			}.execute(sucessObs);
		}
	}
	//------------------------------------------------------------------------ 对外公开的异步消息发送方法 END


	//------------------------------------------------------------------------ 消息发送同步实现方法 START
//	/**
//	 * 发送聊天消息（包括普通文本、图片消息、语音留言消息等）给指定user_id的用户.
//	 * <b>注意：</b>目前普通文本消息为了提升用户体验，提供QoS支持.
//     * <p>
//     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
//     * </p>
//	 *
//	 * @param context
//	 * @param msg 消息内容，纯文本字串，可能是聊天文字、图片文件名或语音文件名等，但一定不是JSON字串
//	 * @param at 此条消息的"@"对象数组（数组单元是被“@”者的uid）
//	 * @return
//	 * @throws Exception
//	 */
//	private static int sendChatMessage(Context context, int msgType, String toGid, String msg, List<String> at, String fingerPrint)
//	{
//		return sendBBSChatMsg_A$TO$SERVER_Message(context, msgType, toGid, msg, at, true, fingerPrint);
//	}
//	/**
//	 * 发送临时聊天消息：由发送人A发给服务端【步骤1/2】.
//     * <p>
//     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
//     * </p>
//	 *
//	 * @param context
//	 * @param msg 消息内容，纯文本字串，可能是聊天文字、图片文件名或语音文件名等，但一定不是JSON字串
//	 * @param at 此条消息的"@"对象数组（数组单元是被“@”者的uid）
//	 * @return
//	 * @throws Exception
//	 */
//	private static int sendBBSChatMsg_A$TO$SERVER_Message(Context context
//			, int msgType, String toGid, String msg, List<String> at, boolean QoS, String fingerPrint)
//	{
//		// 发送消息时，user_id=0即表示发送给服务端
//		return sendBBSChatMsg_A$TO$SERVER_Message(context
//				, constructGroupChatMsgBodyForSend(context, fingerPrint, msgType, toGid, msg, at)
//				, QoS, fingerPrint);
//	}
	/**
	 * 发送群组聊天消息：由发送人A发给服务端【步骤1/2】.
     * <p>
     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
     * </p>
	 * 
	 * @param context
	 * @param tcmd
	 * @return
	 * @throws Exception
	 */
	private static int sendBBSChatMsg_A$TO$SERVER_Message(Context context, MsgBody4Group tcmd
			, boolean QoS, String fingerPrint)
	{
		// 发送消息（群聊/世界频道消息是通过服务端再扩散发送出去的）
		return sendMessage(context, (new Gson().toJson(tcmd))
				, QoS, fingerPrint, UserProtocalsType.MT44_OF_GROUP$CHAT$MSG_A$TO$SERVER);
	}

	/**
	 * 发送消息给指定user_id的用户.
     * <p>
     * 说明：安卓2.3及以后系统中规定：发送网络数据须在单独的线程中。
     * </p>
	 *
	 * @param context
	 * @param message 要发送的文本消息
	 * @return
	 * @throws Exception
	 */
	private static int sendMessage(Context context, String message, boolean QoS, String fingerPrint, int typeu)
	{
		return SendDataHelper.sendMessageImpl(
				// 群聊消息是通过服务端发送出去的，所以此处的目标接收用户id="0"(MobileIMSDK框架中将"0"保留作为服务器)
				context, "0", message, QoS, fingerPrint, typeu);
	}
	//------------------------------------------------------------------------ 消息发送同步实现方法 END


	/**
	 * 构造聊天消息DTO对象.
	 * 
	 * @param context
	 * @param msg 消息内容，纯文本字串，可能是聊天文字、图片文件名或语音文件名等，但一定不是JSON字串
	 * @param at 此条消息的"@"对象数组（数组单元是被“@”者的uid）
	 * @return
	 */
	private static MsgBody4Group constructGroupChatMsgBodyForSend(Context context, String parentFp, int msgType, String toGid, String msg, List<String> at)
	{
		return MsgBody4Group.constructGroupChatMsgBody(msgType
				, MyApplication.getInstance(context).getIMClientManager().getLocalUserInfo().getUser_uid()
				//, MyApplication.getInstance(context).getIMClientManager().getLocalUserInfo().getNickname()
				, GroupsProvider.getMyNickNameInGroupEx(context, toGid)
				, toGid
				, msg
				, parentFp
				, at != null ? at.toArray(new String[]{}) : null
		);
	}

}
